
function createPatchFunction(backend) {

    var i, j;

    // g：cbs={ destory:[],actived:[],create:[] }
    var cbs = {};

    // g：platformModules.concat(baseModules)
    // g：platformModules = [attrs,klass,events,domProps,style]
    // g：baseModules = [ ref, directives ]
    var modules = backend.modules;
    var nodeOps = backend.nodeOps;

    // g：hooks = ['create', 'activate', 'update', 'remove', 'destroy']
    for (i = 0; i < hooks.length; ++i) {

        var current_hook = hooks[i] 
        var cb_hook = cbs[current_hook] = [];
        // g：modules=[events]
        // g：event = { create(){} , destory(){} }
        // g：比如现在 current_hook 是 create，然后从 moudule 的events 中拿 create 的函数
        for (j = 0; j < modules.length; ++j) {

            // g：比如是 events
            var _module = modules[j]

            // g：获取 module 中 的 destory, create,remove 等
            var module_hook = _module[ current_hook ]

            if (isDef(module_hook)) {

                // g：cbs={ destory:[] }
                cb_hook.push(module_hook);

            }
        }
    }

    function emptyNodeAt(elm) {
        return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
    }

    function createElm(
        vnode,insertedVnodeQueue,parentElm,
        refElm,nested,ownerArray,index
    ) {

        if (isDef(vnode.elm) && isDef(ownerArray)) {

            // This vnode was used in a previous render!
            // now it's used as a new node, overwriting its elm would cause
            // potential patch errors down the road when it's used as an insertion
            // reference node. Instead, we clone the node on-demand before creating
            // associated DOM element for it.
            vnode = ownerArray[index] = cloneVNode(vnode);
        }


        vnode.isRootInsert = !nested; // for transition enter check
        
        /***
         * g：如果是 组件，那么就 返回
         * g：1、创建 vm 实例，从另一个维度去 挂载 dom 到父节点
         * g：2、因为已经创建了 dom，所以
         */
        if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) return

        var children = vnode.children;
        var tag = vnode.tag;

        if (isDef(tag)) {
            
            // g：ns 是什么？？？
            vnode.elm = nodeOps.createElement(tag, vnode);
            
            /* istanbul ignore if */
            {   
                // g：先把 子节点插入 vnode.elm，然后再把 vnode.elm 插入parent
                createChildren(vnode, children, insertedVnodeQueue);

                // learn： 插入DOM 节点
                insert(parentElm, vnode.elm, refElm);
            
            }
        } 
        
        else if (isTrue(vnode.isComment)) {
            vnode.elm = nodeOps.createComment(vnode.text);
            insert(parentElm, vnode.elm, refElm);
        } 
        
        else {
            vnode.elm = nodeOps.createTextNode(vnode.text);
            insert(parentElm, vnode.elm, refElm);
        }

    }

    /**
     * 插入节点
     * 1、无兄弟节点，直接插入父节点内
     * 2、有兄弟，插在兄弟前面
     */
    function insert(parent, elm, ref$$1) {

        if (isDef(parent)) {
            if (isDef(ref$$1)) {
                if (ref$$1.parentNode === parent) {
                    nodeOps.insertBefore(parent, elm, ref$$1);
                }
            } else {
                nodeOps.appendChild(parent, elm);
            }
        }

    }

    /***

        * g：创建子节点，如果子节点是数组，则遍历执行 createElm 方法
        * g：如果子节点的 text 属性有数据，则使用 nodeOps.appendChild(...) 在真实 DOM 中插入文本内容
        *
        */
    function createChildren(vnode, children, insertedVnodeQueue) {

        if (Array.isArray(children)) {

            for (var i = 0; i < children.length; ++i) {
                createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
            }

        } else if (isPrimitive(vnode.text)) {

            nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
        }

    }

    // learn: 批量调用 createElm 新建节点
    function addVnodes(
        parentElm, refElm, vnodes, 
        startIdx, endIdx, insertedVnodeQueue
    ) {

        for (; startIdx <= endIdx; ++startIdx) {
            createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx);
        }
    }

    // g：会移除 头和 尾 
    function removeVnodes(
        parentElm, vnodes, 
        startIdx, endIdx
    ) {
        for (; startIdx <= endIdx; ++startIdx) {
            var ch = vnodes[startIdx];
            if (isDef(ch)) {

                // g：ch.tag 存在说明是 vue 组件，不是原生dom 节点
                removeNode(ch.elm);
            }
        }
    }

    function removeNode(el) {
        var parent = nodeOps.parentNode(el);

        // element may have already been removed due to v-html / v-text
        if (isDef(parent)) {
            nodeOps.removeChild(parent, el);
        }
    }

    function sameVnode(a, b) {
        return (
            a.key === b.key && (
                (
                    a.tag === b.tag &&
                    a.isComment === b.isComment &&
                    isDef(a.data) === isDef(b.data) &&
                    sameInputType(a, b)
                ) || (
                    isTrue(a.isAsyncPlaceholder) &&
                    a.asyncFactory === b.asyncFactory &&
                    isUndef(b.asyncFactory.error)
                )
            )
        )
    }

    function sameInputType(a, b) {
        if (a.tag !== 'input') { return true }
        var i;
        var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type;
        var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type;
        return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
    }

    /***
     *
     * g：一个 while 循环 比较更新，一个 for 遍历添加
     *
     */
    function updateChildren(
        parentElm, oldCh, newCh, 
        insertedVnodeQueue, removeOnly
    ) {

        var oldStartIdx = 0;
        var newStartIdx = 0;

        var oldEndIdx = oldCh.length - 1;
        var oldStartVnode = oldCh[0];
        var oldEndVnode = oldCh[oldEndIdx];

        var newEndIdx = newCh.length - 1;
        var newStartVnode = newCh[0];
        var newEndVnode = newCh[newEndIdx];

        var oldKeyToIdx, idxInOld, vnodeToMove, refElm;

        // removeOnly is a special flag used only by <transition-group>
        // to ensure removed elements stay in correct relative positions
        // during leaving transitions
        var canMove = !removeOnly;

        // learn：old 和 new 先 头比头，尾比尾，头比尾，尾比头，实在不行，遍历找相同Vnode
        // learn：不断地更新 OldIndex 和 OldVnode ，newIndex 和 newVnode
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

            // learn：先把 oldStartVnode 和 oldEndVnode 变成 有值

            // g：只有  oldStartIdx  变化
            if (isUndef(oldStartVnode)) {
                oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
            } 
            
            // g：只有  oldEndIdx  变化
            else if (isUndef(oldEndVnode)) {
                oldEndVnode = oldCh[--oldEndIdx];
            } 
            
            // g： newStartIdx 、 oldStartIdx  变化
            else if (sameVnode(oldStartVnode, newStartVnode)) {
                
                patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);

                oldStartVnode = oldCh[++oldStartIdx];
                newStartVnode = newCh[++newStartIdx];

            } 
            
            // g： newEndIdx 、 oldEndIdx  变化
            else if (sameVnode(oldEndVnode, newEndVnode)) {

                patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);

                oldEndVnode = oldCh[--oldEndIdx];
                newEndVnode = newCh[--newEndIdx];

            } 

            
            /**
             * 头比头，尾比尾  和  头比尾，尾比头的区别是，
             * 头比尾，尾比头 的时候，会进行 dom 插入操作，而 头头，尾尾单纯只是比较更新，不会移动dom
             */
            
            // g： newEndIdx  、 oldStartIdx  变化
            else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right

                patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);

                // oldStartVnode 放到 oldEndVnode 后面，还要找到 oldEndValue 后面的节点
                canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));

                oldStartVnode = oldCh[++oldStartIdx];
                newEndVnode = newCh[--newEndIdx];

            } 
            
            // g： newStartIdx 、 oldEndIdx  变化
            else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left

                patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);

                // oldEndVnode 放到 oldStartVnode 前面
                canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);

                oldEndVnode = oldCh[--oldEndIdx];
                newStartVnode = newCh[++newStartIdx];

            }
            
            // g：只有  newStartIdx  变化
            else {

                // learn：oldKeyToIdx 是一个 把 Vnode 的 key 和 index 转换的 map
                if (isUndef(oldKeyToIdx)) { 
                    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); 
                }

                // learn：使用 newStartVnode 去 OldMap 中寻找 相同节点
                idxInOld = isDef(newStartVnode.key) ?
                    oldKeyToIdx[newStartVnode.key] :

                    // learn：孩子一个个比对，如果 newStartVnode 和 旧孩子某个一样，就返回 该旧孩子的 index
                    findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
                
                //  learn：新孩子中，存在一个新节点，老节点中没有
                if (isUndef(idxInOld)) { // New element
                    
                    // learn：createElm 什么作用，需要查看
                    // learn： 把  newStartVnode 插入 oldStartVnode 的前面
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);

                } 
                
                else {
                    
                    // learn： 找到 oldCh 中 和 newStartVnode 一样的节点
                    vnodeToMove = oldCh[idxInOld];

                    if (sameVnode(vnodeToMove, newStartVnode)) {

                        patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
                        
                        // learn：删除这个 index
                        oldCh[idxInOld] = undefined;

                        // learn：把 vnodeToMove 移动到  oldStartVnode 前面
                        canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);

                    } 
                    
                    // g：只能创建一个新节点插入到 parentElm 的子节点中
                    else {

                        // same key but different element. treat as new element
                        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);

                    }

                }

                // 更新 newStartIdx
                newStartVnode = newCh[++newStartIdx];
            }
        }

        /***
         * 很多种情况
         * 
         * g：1、oldCh 存在，但是 节点全部一样，那么 oldStartIdx 和 oldEndIdx  没有变化，
         *    g：newCh 逐个 跟 oldCh 比较，因为不一一样，所有逐个添加进了parent
         * 
         * g： 
         *
         */
        // g：说明老节点比对完了，但是新节点还有多的，需要把新节点
        if (oldStartIdx > oldEndIdx) {

            // ？：newCh 中的 elm 绑定过了dom 了吗
            refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;

            addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
        } 
        
        // g：说明新节点比对完了，老节点还有多，需要删除多余的 老节点
        // g：一种情况是 oldCh 和 newCh 全不一样，newStartIdx 一直变化，一直把 newStartNode 添加进去，然后需要移除 oldCh
        else if (newStartIdx > newEndIdx) {
            removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
        }

    }

    function findIdxInOld(node, oldCh, start, end) {

        for (var i = start; i < end; i++) {
            var c = oldCh[i];
            if (isDef(c) && sameVnode(node, c)) { return i }
        }
    }

    // learn：insertedVnodeQueue 不知道是什么
    // learn：作用感觉是 比较两个 Vnode 的 children
    function patchVnode(
        oldVnode, vnode, 
        insertedVnodeQueue, removeOnly
    ) {

        // console.log(vnode.tag)
        if(vnode.tag && vnode.tag.indexOf("test")>-1){
            // console.log(111)
        }

        // g：更新
        if (oldVnode === vnode) return

        var elm = vnode.elm = oldVnode.elm;

        if (isTrue(oldVnode.isAsyncPlaceholder)) {

            if (isDef(vnode.asyncFactory.resolved)) {
                hydrate(oldVnode.elm, vnode, insertedVnodeQueue);
            } else {
                vnode.isAsyncPlaceholder = true;
            }
            return
        }

        // reuse element for static trees.
        // note we only do this if the vnode is cloned -
        // if the new node is not cloned it means the render functions have been
        // reset by the hot-reload-api and we need to do a proper re-render.
        if (isTrue(vnode.isStatic) &&
            isTrue(oldVnode.isStatic) &&
            vnode.key === oldVnode.key &&
            (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
        ) {
            vnode.componentInstance = oldVnode.componentInstance;
            return
        }

        var i;
        var data = vnode.data;

        if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
            i(oldVnode, vnode);
        }

        var oldCh = oldVnode.children;
        var ch = vnode.children;

        if (isDef(data) && isPatchable(vnode)) {

            for (i = 0; i < cbs.update.length; ++i) { var name=cbs.update[i]; name(oldVnode, vnode); }
            if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }
        }
        
        // g：更新children
        if (isUndef(vnode.text)) {

            // 存在 oldCh 和 ch 时
            if (isDef(oldCh) && isDef(ch)) {

                // g：更新
                if (oldCh !== ch) { 
                    updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); 
                }

            } 
            
            // 存在 newCh 时，oldCh 只能是不存在，如果存在，就跳到上面的条件了
            else if (isDef(ch)) {

                if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
                
                // g：更新
                addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);

            } 
            
            else if (isDef(oldCh)) {

                // g：更新
                removeVnodes(elm, oldCh, 0, oldCh.length - 1);
            } 
            
            else if (isDef(oldVnode.text)) {

                // g：更新
                nodeOps.setTextContent(elm, '');
            }

        } 
        
        else if (oldVnode.text !== vnode.text) {
            // g：更新
            nodeOps.setTextContent(elm, vnode.text);
        }

        // console.log("更新节点",vnode.tag)

        // g：执行 postpatch 和 指令的 componentUpdated
        if (isDef(data)) {
            if (isDef(i = data.hook) && isDef(i = i.postpatch)) { 
                i(oldVnode, vnode); 
            }
        }

    }

    // invokeDestroyHook,invokeInsertHook,insertedVnodeQueue
    return function patch(
        oldVnode, vnode, hydrating, 
        removeOnly, parentElm, refElm
    ) {

        var insertedVnodeQueue = [];

        // g：没有旧节点，直接生成新f节点
        // g：oldVnode 有可能是原生 dom，当 不是组件时，oldVnode 不是 undefined
        if (isUndef(oldVnode)) {
            createElm(vnode, insertedVnodeQueue, parentElm, refElm);
        } 
        
        else {

            /***
             * g：Vnode 实例没有 nodeType 这个属性，
             * g： 这是为了区分 dom 节点 和 Vnode 实例
             ***/
            var isRealElement = isDef(oldVnode.nodeType);

            // g：不是 DOM 节点 + 且是一样 Vnode
            if (!isRealElement && sameVnode(oldVnode, vnode)) {

                // patch existing root node
                patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
            } 
            
            else {

                // g：原生 dom，需要注册为 Vnode 实例
                if (isRealElement) {

                    // either not server-rendered, or hydration failed.
                    // create an empty node and replace it
                    // oldVnode 原先是 dom 节点，注册成为 Vnode 实例
                    oldVnode = emptyNodeAt(oldVnode);
                }

                // replacing existing element
                var oldElm = oldVnode.elm;
                var parentElm$1 = nodeOps.parentNode(oldElm);

                // create new node
                createElm(
                    vnode,
                    insertedVnodeQueue,
                    // extremely rare edge case: do not insert if old element is in a
                    // leaving transition. Only happens when combining transition +
                    // keep-alive + HOCs. (#4590)
                    oldElm._leaveCb ? null : parentElm$1,
                    nodeOps.nextSibling(oldElm)
                );

                // destroy old node
                if (isDef(parentElm$1)) {
                    removeVnodes(parentElm$1, [oldVnode], 0, 0);
                } 
            }
        }
        return vnode.elm
    }
}